#include "catch.hpp"

#include "common/subprocess.h"
#include "common/str.h"


#define EXPECT_SPAWN_ERROR(err, errMsg, cmd, ...) \
do { \
    try { \
        Subprocess proc(std::vector<std::string>{ (cmd), ## __VA_ARGS__ }); \
        FAIL("expected an error when running " << (cmd)); \
    } \
    catch (const SubprocessSpawnError& ex) { \
        REQUIRE((err) == ex.errnoValue()); \
        if (!str::contains(ex.what(), errMsg)) { \
            FAIL("failed to find \"" << (errMsg) << \
                 "\" in exception: \"" << ex.what() << "\""); \
        } \
    } \
} while (0)

TEST_CASE("subprocess tests", "[subprocess]") {
    using namespace std;
    using namespace evm;

    {
        Subprocess proc(vector<string>{"/bin/true"});
        REQUIRE(proc.wait().exitStatus() == 0);
    }

    {
        Subprocess proc(vector<string>{"/bin/true"});
        REQUIRE_NOTHROW(proc.waitChecked());
    }

    {
        Subprocess proc(vector<string>{"/bin/false"});
        REQUIRE(proc.wait().exitStatus() == 1);
    }

    {
        Subprocess proc(vector<string>{"/bin/false"});
        REQUIRE_THROWS_AS(proc.waitChecked(), CalledProcessError);
    }

    {
        ProcessReturnCode retcode;
        REQUIRE(retcode.notStarted());
    }


    {
        Subprocess old_proc(std::vector<std::string>{"/bin/true"});
        REQUIRE(old_proc.returnCode().running());
        auto new_proc = std::move(old_proc);
        REQUIRE(old_proc.returnCode().notStarted());
        REQUIRE(new_proc.returnCode().running());
        REQUIRE(new_proc.wait().exitStatus() == 0);
    }

    {
        EXPECT_SPAWN_ERROR(ENOENT, "failed to execute /no/such/file:", "/no/such/file");
        EXPECT_SPAWN_ERROR(EACCES, "failed to execute /etc/passwd:", "/etc/passwd");
        EXPECT_SPAWN_ERROR(ENOTDIR, "failed to execute /etc/passwd/not/a/file:", "/etc/passwd/not/a/file");
    }

    {
        // The filesystem root normally lacks a 'true' binary
        REQUIRE(chdir("/") == 0);
        EXPECT_SPAWN_ERROR(ENOENT, "failed to execute ./true", "./true");
        // The child can fix that by moving to /bin before exec().
        Subprocess proc("./true", Subprocess::Options().chdir("/bin"));
        REQUIRE(proc.wait().exitStatus() == 0);
    }

    /*{
        Subprocess proc(std::vector<std::string>{"/bin/echo", "-n", "foo", "bar"},
                        Subprocess::pipeStdout());
        auto p = proc.communicate();
        REQUIRE(p.first == "foo bar");
        proc.waitChecked();
    }*/

    {
        REQUIRE_NOTHROW(execute("ls"));
    }
}
